package com.warmheart.core.util.rsa;

import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.validation.constraints.NotNull;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RSAUtil {

    private static Logger log = LoggerFactory.getLogger(RSAUtil.class);

    /** 加密解密算法名称 */
    private static final String ALGORITHM = "RSA";

    /**
     * 签名算法名称
     */
    public static final String SIGN_ALGORITHMS = "MD5withRSA";

    /**
     * 密钥工厂
     */
    private static KeyFactory keyFactory = null;

    /** Base64 编码/解码器 JDK1.8 */
    private static Base64.Decoder decoder = Base64.getDecoder();

    /** 初始化密钥工厂 */
    static {
        try {
            keyFactory = KeyFactory.getInstance(ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            log.error("RSAUtil[ERROR]:e={}", e);
        }
    }

    /**
     * 私有构造器，避免该类被作为对象使用
     * <p>
     * Title:
     * </p>
     * <p>
     * Description:
     * </p>
     * 
     * @author ZhangJianyang
     */
    private RSAUtil() {
    }

    private static RSAPublicKey getPublicKey(String publicKeyString) {
        if (StringUtils.isBlank(publicKeyString)) {
            return null;
        }
        try {
            byte[] keyBytes = decoder.decode(publicKeyString);
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
            return (RSAPublicKey) keyFactory.generatePublic(x509EncodedKeySpec);
        } catch (InvalidKeySpecException e) {
            log.error("getPublicKey()#" + e.getMessage(), e);
        }
        return null;
    }

    /**
     * * RSA公钥加密 * @param content 等待加密的数据 * @param publicKey RSA 公钥 if null then
     * getPublicKey() * @return 加密后的密文(16进制的字符串)
     */
    public static String encryptByPublic(String content, String publicKeyStr) {
        try {
            PublicKey publicKey = getPublicKey(publicKeyStr);
            if (publicKey == null) {
                return null;
            }
            byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            // 该密钥能够加密的最大字节长度
            int splitLength = ((RSAPublicKey) publicKey).getModulus().bitLength() / 8 - 11;
            byte[][] arrays = splitBytes(bytes, splitLength);
            StringBuffer stringBuffer = new StringBuffer();
            for (byte[] array : arrays) {
                stringBuffer.append(bytesToHexString(cipher.doFinal(array)));
            }
            return stringBuffer.toString();
        } catch (NoSuchAlgorithmException e) {
            log.error("encrypt()#NoSuchAlgorithmException", e);
        } catch (NoSuchPaddingException e) {
            log.error("encrypt()#NoSuchPaddingException", e);
        } catch (InvalidKeyException e) {
            log.error("encrypt()#InvalidKeyException", e);
        } catch (BadPaddingException e) {
            log.error("encrypt()#BadPaddingException", e);
        } catch (IllegalBlockSizeException e) {
            log.error("encrypt()#IllegalBlockSizeException", e);
        }
        return null;
    }

    private static RSAPrivateKey getPrivateKey(String privateKeyString) {
        if (StringUtils.isBlank(privateKeyString)) {
            return null;
        }
        try {
            byte[] keyBytes = decoder.decode(privateKeyString);
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
            return (RSAPrivateKey) keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (InvalidKeySpecException e) {
            log.error("getPrivateKey()#" + e.getMessage(), e);
        }
        return null;
    }

    /**
     * * RSA私钥解密 * @param content 等待解密的数据 * @param privateKey RSA 私钥 if null
     * then getPrivateKey() * @return 解密后的明文
     */
    public static String decryptByPrivate(String content, @NotNull String privateKeyStr) {
        try {
            PrivateKey privateKey = getPrivateKey(privateKeyStr);
            if (privateKey == null) {
                return null;
            }
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            // 该密钥能够加密的最大字节长度
            int splitLength = ((RSAPrivateKey) privateKey).getModulus().bitLength() / 8;
            byte[] contentBytes = hexStringToBytes(content);
            byte[][] arrays = splitBytes(contentBytes, splitLength);
            StringBuffer stringBuffer = new StringBuffer();
            for (byte[] array : arrays) {
                stringBuffer.append(new String(cipher.doFinal(array)));
            }
            return stringBuffer.toString();
        } catch (NoSuchAlgorithmException e) {
            log.error("encrypt()#NoSuchAlgorithmException", e);
        } catch (NoSuchPaddingException e) {
            log.error("encrypt()#NoSuchPaddingException", e);
        } catch (InvalidKeyException e) {
            log.error("encrypt()#InvalidKeyException", e);
        } catch (BadPaddingException e) {
            log.error("encrypt()#BadPaddingException", e);
        } catch (IllegalBlockSizeException e) {
            log.error("encrypt()#IllegalBlockSizeException", e);
        }
        return null;
    }

    /**
     * * 根据限定的每组字节长度，将字节数组分组 * @param bytes 等待分组的字节组 * @param splitLength 每组长度 * @return
     * 分组后的字节组
     */
    private static byte[][] splitBytes(byte[] bytes, int splitLength) {
        // bytes与splitLength的余数
        int remainder = bytes.length % splitLength;
        // 数据拆分后的组数，余数不为0时加1
        int quotient = remainder != 0 ? bytes.length / splitLength + 1 : bytes.length / splitLength;
        byte[][] arrays = new byte[quotient][];
        byte[] array = null;
        for (int i = 0; i < quotient; i++) {
            // 如果是最后一组（quotient-1）,同时余数不等于0，就将最后一组设置为remainder的长度
            if (i == quotient - 1 && remainder != 0) {
                array = new byte[remainder];
                System.arraycopy(bytes, i * splitLength, array, 0, remainder);
            } else {
                array = new byte[splitLength];
                System.arraycopy(bytes, i * splitLength, array, 0, splitLength);
            }
            arrays[i] = array;
        }
        return arrays;
    }

    /** * 将字节数组转换成16进制字符串 * @param bytes 即将转换的数据 * @return 16进制字符串 */
    private static String bytesToHexString(byte[] bytes) {

        StringBuffer sb = new StringBuffer(bytes.length);
        String temp = null;
        for (int i = 0; i < bytes.length; i++) {

            temp = Integer.toHexString(0xFF & bytes[i]);
            if (temp.length() < 2) {

                sb.append(0);
            }
            sb.append(temp);
        }
        return sb.toString();
    }

    /** * 将16进制字符串转换成字节数组 * @param hex 16进制字符串 * @return byte[] */
    private static byte[] hexStringToBytes(String hex) {
        int len = (hex.length() / 2);
        hex = hex.toUpperCase();
        byte[] result = new byte[len];
        char[] chars = hex.toCharArray();
        for (int i = 0; i < len; i++) {
            int pos = i * 2;
            result[i] = (byte) (toByte(chars[pos]) << 4 | toByte(chars[pos + 1]));
        }
        return result;
    }

    /** * 将char转换为byte * @param c char * @return byte */
    private static byte toByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }
}
